package client;

import capsules.CapsuleSpecification;
import capsules.CapsuleState;
import communication.ConnectionException;
import constans.CapsuleConstans;
import constans.ConfigurationConstans;
import constans.SizeConstans;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.GeneralSecurityException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import utils.MachineUtils;

public class ReplicationProcess extends Process{


    protected ReplicationProcess(RarcServer rarcServer, Configuration configuration,
            Logger logger, AtomicBoolean isExecute ){
        super.rarcServer = rarcServer;
        super.configuration = configuration;
        super.isExecute = isExecute;
        super.logger = logger;
    }


    protected void execute()
            throws ConnectionException, GeneralSecurityException{

        loadCapsules();

		synchronizedRegister();

        sendOfferedSpaceToRarcServer(configuration.getOfferSpace());

		while( existsFreeSpaceReplicate() && isExecute.get()){

            CapsuleSpecification capsuleSpec;

            if( (capsuleSpec = getCapsuleForReplication())!=null)
            {
                try
                {
                    createCapsuleForReplication(capsuleSpec);
                }
                catch (IOException e)
                {
                    break;
                }
            }
            else{
                break;
            }
        }


       if(capsules!=null)
       {
            for(String capsule:capsules)
            {
                if(isExecute.get())
                {
                    try
                    {
                        CapsuleState capsuleState = getCapsuleState(capsule);

                        if (capsuleState.equals(CapsuleState.INCORRECT))
                        {
                            MachineUtils.deleteFile(ConfigurationConstans.LOCAL_DIRECTORY, capsule);
                        }
                        else if(capsuleState.equals(CapsuleState.INCOMPLETE))
                        {
                            doDownload(capsule);
                        }
                    }
                    catch (IOException ex)
                    {
                    
                    }
            }else{
                break;
			}
		 }
       }
    }



    private void loadCapsules() {

        logger.log(Level.INFO,"Load the backup files");

        String [] filesnames = MachineUtils.getFilesNames(ConfigurationConstans.LOCAL_DIRECTORY,
                                CapsuleConstans.CAPSULE_EXTENSION);

        if(filesnames!=null){
            capsules = new ArrayList<String>();
            for(int i = 0;i < filesnames.length ;i++){
                capsules.add(filesnames[i]);
            }
        }else{
            capsules = null;
        }
    }


    private void synchronizedRegister() {

        try {
            register = new Register();
            register.load(ConfigurationConstans.REGISTER_FILENAME);
        } catch (IOException e){
            logger.log(Level.WARNING,e.getMessage());
        }

		Register registerTemp = new Register();

		long offset;

        if(capsules!=null){

            for(int i = 0; i < capsules.size() ; i++){

                try{
                    if( ( offset = register.getOffset(capsules.get(i))) < 0)
                        offset = 0;

                    registerTemp.insert(capsules.get(i), offset);
                } catch (NumberFormatException e) {
                    registerTemp.insert(capsules.get(i),0L);
                }
            }
        }

        register = registerTemp;
	}


    private boolean existsFreeSpaceReplicate(){

        DecimalFormat df = new DecimalFormat("0.00");

        long offerSpace = configuration.getOfferSpace();
        long capsuleLength = CapsuleConstans.CAPSULE_LENGTH;

        String [] filesname = (String []) capsules.toArray (new String [capsules.size()]);
        long freeSpace = offerSpace - MachineUtils.getUsedSpace(filesname);


        return (freeSpace >= capsuleLength) && (MachineUtils.getFreeSpace(ConfigurationConstans.LOCAL_DIRECTORY) >= capsuleLength);

	}


    private void doDownload(String capsule)
            throws ConnectionException, FileNotFoundException,
            IOException{

        logger.log(Level.INFO,"Download backup file: "+capsule);

		long offset = register.getOffset(capsule);
		long length = MachineUtils.getFileLength(ConfigurationConstans.LOCAL_DIRECTORY, capsule);

        int noSave = 1000;

		RandomAccessFile capsuleStream = null;
		try{

			capsuleStream = new RandomAccessFile(capsule, "rw");
			capsuleStream.seek(offset);


			rarcServer.downloadCapsule(capsule, offset);

			int numRead = 0;
			byte[] buffer = new byte[(int)SizeConstans.KByte];

			while (offset < length && isExecute.get()) {

				numRead = rarcServer.getCapsuleBytes(buffer);

				capsuleStream.write(buffer, 0, numRead);
				offset+=numRead;

                noSave--;

                if(noSave == 0){

                    register.update(capsule,offset);
                    register.save(ConfigurationConstans.REGISTER_FILENAME);

                    noSave = 1000;
                }
			}

			register.update(capsule,offset);
			register.save(ConfigurationConstans.REGISTER_FILENAME);


			capsuleStream.close();

		} catch (FileNotFoundException e) {
			try{
				if(capsuleStream!=null){
					capsuleStream.close();
					capsuleStream = null;
				}
			}catch(IOException e1){}
			throw new FileNotFoundException();
		} catch (ConnectionException e) {
			try{
				if(capsuleStream!=null){
					capsuleStream.close();
					capsuleStream = null;
				}
			}catch(IOException e1){}
			register.update(capsule,offset);
			register.save(ConfigurationConstans.REGISTER_FILENAME);
			throw new ConnectionException();
		} catch (IOException e) {
			try{
				if(capsuleStream!=null){
					capsuleStream.close();
					capsuleStream = null;
				}
			}catch(IOException e1){}
			register.update(capsule,offset);
			register.save(ConfigurationConstans.REGISTER_FILENAME);
			throw new IOException();
		} catch (IndexOutOfBoundsException e) {
			try{
				if(capsuleStream!=null){
					capsuleStream.close();
					capsuleStream = null;
				}
			}catch(IOException e1){}
			register.update(capsule,offset);
			register.save(ConfigurationConstans.REGISTER_FILENAME);
			throw new ConnectionException();
		}
	}


    private CapsuleState getCapsuleState(String capsuleName)
            throws GeneralSecurityException, IOException,
            ConnectionException{

        long length = MachineUtils.getFileLength(ConfigurationConstans.LOCAL_DIRECTORY, capsuleName);
        byte[] hash = MachineUtils.getHashFile(ConfigurationConstans.LOCAL_DIRECTORY, capsuleName);

        CapsuleSpecification capsuleSpecification = new CapsuleSpecification(capsuleName,
                            length, hash, register.getOffset(capsuleName));

        CapsuleState state = rarcServer.getCapsuleState(capsuleSpecification);

        logger.log(Level.INFO,"State of backup file("+capsuleName+"): "+state.toString());
        
        return state;

    }

    
    private void sendOfferedSpaceToRarcServer(long offeredSpace)
            throws ConnectionException{
        logger.log(Level.INFO,"Send offered sapce: "+offeredSpace+" bytes");
        rarcServer.sendOfferedSpace(offeredSpace);
    }

    
    private CapsuleSpecification getCapsuleForReplication()
            throws ConnectionException{

        CapsuleSpecification specification = rarcServer.getCapsuleSpecification();
        logger.log(Level.INFO,"Replicate backup file: "+specification.getName());
        
        return specification;
    }

    
    private void createCapsuleForReplication(CapsuleSpecification capsuleSpec)
            throws IOException{

        if(!MachineUtils.existsFile(ConfigurationConstans.LOCAL_DIRECTORY,
            capsuleSpec.getName()))
        {
            MachineUtils.createEmptyFile(ConfigurationConstans.LOCAL_DIRECTORY,
                capsuleSpec.getName(), capsuleSpec.getLength());

            logger.log(Level.INFO,"Create backup file: "+capsuleSpec.getName());
            capsules.add(capsuleSpec.getName());

            register.insert(capsuleSpec.getName(), 0L);
            register.save(ConfigurationConstans.REGISTER_FILENAME);
            logger.log(Level.INFO,"Registre backup file: "+capsuleSpec.getName());
        }       
    }

}
